#include "../../include/fileSystem/directory.h"
#include "../../include/fileSystem/fileSystem.h"
#include "../../include/fileSystem/fileDescriptor.h"
#include "../../include/process.h"
#include "../../include/kc.h"

extern process_t processes[];	//lista de procesos
extern int currProc;			//proceso actual

static void fillDirEntry(dirEntry * dest, char * name, int inodeNum, char type);

void
addDirEntry(dirEntry * dir, char * name, int inodeNum, int * dirSize, char type) {
	int cant = *dirSize/sizeof(dirEntry);

    fillDirEntry(&dir[cant], name, inodeNum, type);
    (*dirSize)+= sizeof(dirEntry);
}

//arma una entrada de directorio
static void
fillDirEntry(dirEntry * dest, char * name, int inodeNum, char type) {
	int nameLen = strlen(name);

	//copio el nombre (hasta FILE_NAME_LEN de largo) y el inodo
	nameLen = (nameLen <= FILE_NAME_LEN)? nameLen : FILE_NAME_LEN;
	memcpy((char*)dest, name, nameLen + 1);
	memcpy((char*)dest + FILE_NAME_LEN + 1, &inodeNum, sizeof(int));

	//copio el tipo de entrada que es
	*((char*)dest + FILE_NAME_LEN + 1 + sizeof(int)) = type;
}

int
rmDirEntry(dirEntry * dir, char * name, int * dirSize) {
	int i, cant = *dirSize/sizeof(dirEntry);

	for (i = 0; i < cant; i++) {
		if (strcmp(dir[i].name, name) == 0) {
			int inodeNum = dir[i].inode;

			//actualizo el tamaño del directorio
			(*dirSize)-= sizeof(dirEntry);

			//si el borrado no era la ultima entrada, la sobreescribo
			if ( (i * sizeof(dirEntry)) != (*dirSize))
				memcpy(&dir[i], &dir[cant-1], sizeof(dirEntry));

			return inodeNum;
		}
	}

	return -1;
}

int
searchDir(dirEntry * dir, int dirSize, char * name) {
	int i, cant = dirSize/sizeof(dirEntry);

	//considero un nombre vacio como una referencia a mi mismo
	if (*name == '\0')
		name = ".";

	for (i = 0; i < cant; i++) {
		if (strcmp(dir[i].name, name) == 0)
			return dir[i].inode;
	}

	return -1;
}

char *
parsePath(char * dest, char * path) {
	int i = 0;
	while (i < FILE_NAME_LEN && path[i] != '/' && path[i] != '\0') {
		dest[i] = path[i];
		i++;
	}
	dest[i] = '\0';

	//si path termino en un '/', lo dejo apuntando al proximo caracter
	while (path[i] == '/')
		i++;
	return path + i;
}

void
getNameFromPath(char * dest, char * filePath) {
	int i = strlen(filePath) - 1;

	dest[0] = '\0';

	//si no me enviaron nada, salgo
	if (i < 0)
		return;

	//saco los '/' finales
	while (i >= 0 && filePath[i] == '/')
		filePath[i--] = '\0';

	//si solo habia '/', dejo una y salgo
	if (i < 0) {
		filePath[0] = '/';
		return;
	}

	//hasta el proximo '/' va a estar el nombre que va en dest
	while (i >= 0 && filePath[i] != '/')
		i--;

	strncpy(dest, &filePath[i+1], FILE_NAME_LEN);
	dest[FILE_NAME_LEN] = '\0';

	filePath[i+1] = '\0';

	//si consumi todo el path, salgo
	if (i < 0) {
		return;
	}

	//saco los nuevos '/' finales
	while (i >= 0 && filePath[i] == '/')
		filePath[i--] = '\0';

	//si solo quedaban '/', dejo una y salgo
	if (i < 0) {
		filePath[0] = '/';
	}
	return;
}

int
renameDirEntry(inodeTableEntry * dirInode, char * oldName, char * newName) {
	//no dejo renombrar los directorios basicos
	if (*oldName == '\0' || strcmp(oldName, ".") == 0 ||
			strcmp(oldName, "..") == 0 ) {
		return -1;
	}

	//busco el bloque del directorio
	dirEntry * memDir = (dirEntry *)getBlock(dirInode->inode.blockNum[0]);

	int i, cant = dirInode->inode.fileSize/sizeof(dirEntry);
	int entryNum = -1;

	//ciclo en el directorio, buscando oldName, y ademas viendo que newName
	//no exista (sino salgo con error)
	for (i = 0; i < cant; i++) {
		//si encuentro oldName, guardo la entrada y sigo viendo que newName
		//no exista. no chequeo mas por oldName
		if (entryNum == -1 && strcmp(memDir[i].name, oldName) == 0) {
			entryNum = i;
		}
		else if (strcmp(memDir[i].name, newName) == 0) {
			//encontre el nombre nuevo, asi que salgo con error
			putBlock(dirInode->inode.blockNum[0], (char *)memDir);
			return -1;
		}
	}

	//si encontre una entrada valida
	if (entryNum != -1) {
		//cambio el nombre
		strcpy(memDir[entryNum].name, newName);
		//escribo el bloque actualizado
		writeBlock(dirInode->inode.blockNum[0], (char *)memDir);
	}

	putBlock(dirInode->inode.blockNum[0], (char *)memDir);

	return entryNum;
}

char *
getcwdImpl(char * name, int size) {
	if (findCurrDirPath(name, size) == -1)
		return NULL;

	return name;
}

int
mkdirImpl(char * pathName, short int mode) {
	int parentInodeNum = -1;
	inodeTableEntry * inode;

	//trato de crear el inodo del nuevo dir en base al path que me pasaron
	//al pasarle O_CREAT | O_EXCL a la funcion, si el dir existia esto va a fallar
	if ( (inode = createInode(pathName, &parentInodeNum,
			mode | O_CREAT | O_EXCL)) == NULL) {
		return -1;
	}

	//busco el bloque del directorio
	dirEntry * memDir = (dirEntry *)getBlock(inode->inode.blockNum[0]);

	//agrego las entradas basicas de '.' y '..'
	addDirEntry(memDir, ".", inode->inodeNum, &inode->inode.fileSize, 'd');
	addDirEntry(memDir, "..", parentInodeNum, &inode->inode.fileSize, 'd');

	//escribo el bloque y lo devuelvo
	writeBlock(inode->inode.blockNum[0], (char *)memDir);
	putBlock(inode->inode.blockNum[0], (char *)memDir);

	//seteo el modo
	inode->inode.mode = 0 | DIR_BIT;

	//escribo el inodo y lo devuelvo
	writeInode(inode->inodeNum, &inode->inode);
	putInode(inode);

	return 1;
}

int
opendirImpl(char * pathName) {
	inodeTableEntry * dirInode;

	//busco el inodo perteneciente al directorio en pathName
	dirInode = getInode(pathName);
	if (dirInode == NULL)
		return -1;

	//si lo que encontre no es un directorio, lo devuelvo y salgo
	if (!(dirInode->inode.mode & DIR_BIT)) {
		putInode(dirInode);
		return -1;
	}

	//busco un fileDescriptor del proceso libre para guardar el inodo
	int fd = setFileDes(dirInode, 0, 0);
	if (fd == -1) {
		putInode(dirInode);
		return -1;
	}

	//devuelvo el fd del inodo del directorio
	return fd;
}

void
closedirImpl(int fd) {
	inodeTableEntry * dirInode;
	if ( (dirInode = getFdInode(fd)) == NULL)
		return;
	if (!(dirInode->inode.mode & DIR_BIT))
		return;

	freeFileDes(fd);
}

int
rmdirImpl(const char * pathParam){
	return doUnlink(pathParam, DIR_UNLINK);
}

int
readdirImpl(int fd, dirEntry * entry) {
	inodeTableEntry * dirInode;

	if ( (dirInode = getFdInode(fd)) == NULL)
		return -1;

	if (!(dirInode->inode.mode & DIR_BIT))
		return -1;

	//obtengo la posicion actual del archivo
	int filePos = getFilePos(fd);

	//si llegue al final del directorio, devuelvo -1
	if (filePos >= dirInode->inode.fileSize)
		return -1;

	//busco el bloque del directorio
	dirEntry * memDir = (dirEntry *)getBlock(dirInode->inode.blockNum[0]);

	//dejo en la direccion pasada por parametro la entrada de directorio
	memcpy(entry, (char *)memDir + filePos, sizeof(dirEntry));

	//devuelvo el bloque del directorio
	putBlock(dirInode->inode.blockNum[0], (char *)memDir);

	//avanzo la posicion del archivo
	filePos += sizeof(dirEntry);
	updateFileDes(fd, filePos);

	return 1;
}

int
chdirImpl(char * path) {
	inodeTableEntry * dirInode;

	//busco el inodo perteneciente al directorio en path
	dirInode = getInode(path);
	if (dirInode == NULL)
		return -1;

	//si lo que encontre no es un directorio, lo devuelvo y salgo
	if (!(dirInode->inode.mode & DIR_BIT)) {
		putInode(dirInode);
		return -1;
	}

	//si el inodo del currDir no es nulo, lo devuelvo
	if (processes[currProc].currDir != NULL)
		putInode(processes[currProc].currDir);

	processes[currProc].currDir = dirInode;

	return 1;
}

